﻿using Newtonsoft.Json;
using NumSharp;
using System.Diagnostics;
using System.Linq;
using YamlDotNet.Core.Tokens;

namespace Wargame_V3_1
{
    public class Map
    {
        private int maxRow;// 最大行
        private int maxCol;// 最大列
        private int elevGrade;// 高程
        private int width;  // 六角格的半径，默认200m
        private Hex[,] hexes;// 保存了一个六角格的全部信息
        private List<List<List<List<float[]>>>> cost;// 通行代价
        /* 第一维：[0-车辆机动, 1-车辆行军, 2-步兵机动, 3-空中机动]
        [
            [
                [
                    { "能通行的邻域坐标 int " : "通行代价 float" }
                ]
            ]
        ]
        */
        public NDArray see;// 通视数据
        /* 五维数组，第一维(旧版)[0]：地对地、[1]：空对空、[2]：地对空，第一维新版有10种取值
        [
            [
                [
                    [
                        [
                            can_see: boolean
                        ]
                    ]
                ]
            ]
        ]
        */

        public int MaxRow { get => maxRow; }

        public int MaxCol { get => maxCol; }

        public int ElevGrade { get => elevGrade; }

        public int Width { get => width; }

        public Hex this[int row, int col]  // 取代原来的basic字段
        {
            get
            {
                if (IsValid(row, col)) return hexes[row, col];// 如果是合法六角格坐标，返回六角格信息
                return null;// 取六角格时，如果输入坐标超出地图范围，将得到null值
            }
            set
            {
                if (IsValid(row, col)) hexes[row, col] = value;// 如果是合法六角格坐标，则设置六角格信息
            }
        }


        /// <summary>
        /// 从路径path中导入地图文件，包括basic.json、cost.pickle和see.npz
        /// </summary>
        /// <param name="path"></param>
        public Map(string path)
        {
            // 从basic.json文件中导入数据
            using (StreamReader r = new StreamReader(path + @"\basic.json"))// 路径下的basic.json文件
            {
                string json = r.ReadToEnd();// 读取流的当前位置到结尾的所有字符
                Dictionary<string, object> data_dict = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);// 反序列化为字典类型数据
                hexes = JsonConvert.DeserializeObject<Hex[,]>(data_dict["map_data"].ToString());// 反序列化data_dict中以map_data为键的数据，保存为Hex[,]类型
                if (data_dict.ContainsKey("ele_grade")) elevGrade = JsonConvert.DeserializeObject<int>(data_dict["ele_grade"].ToString());// 反序列化data_dict中以ele_grade为键的数据，保存为int类型
                else elevGrade = 20;  // 默认20m的高差
                if (data_dict.ContainsKey("radius")) width = JsonConvert.DeserializeObject<int>(data_dict["width"].ToString());// 反序列化data_dict中以width为键的数据，保存为int类型
                else width = 200;  // 默认200m的半径
                maxRow = hexes.GetLength(0);// 保存最大行数
                maxCol = hexes.GetLength(1);// 保存最大列数
            }

            // 从cost.json文件中导入数据
            using (StreamReader r = new StreamReader(path + @"\cost.json"))// 路径下的cost.json文件
            {
                string json = r.ReadToEnd();// 读取流的当前位置到结尾的所有字符
                cost = JsonConvert.DeserializeObject<List<List<List<List<float[]>>>>>(json);// 反序列化
            }

            // 从see.npz文件中导入数据
            var data_test = np.Load_Npz<bool[,,,,]>(path + @"\see.npz");// 路径下的see.npz文件
            see = data_test["data.npy"];
        }

        /// <summary>
        /// 检查坐标pos是否在地图内
        /// </summary>
        /// <param name="pos"></param>
        /// <returns></returns>
        private bool IsValid(int[] pos)
        {
            int row = pos[0];// 取得行
            int col = pos[1];// 取得列
            if ((row >= 0 && row < maxRow) && (col >= 0 && col < maxCol)) return true;// 行数不超过最大行，列数不超过最大列
            else
            {
                Trace.TraceWarning($"输入坐标[{row}, {col}]超出地图范围");
                return false;
            }
        }

        private bool IsValid(int row, int col)
        {
            if ((row >= 0 && row < maxRow) && (col >= 0 && col < maxCol)) return true;// 行数不超过最大行，列数不超过最大列
            else
            {
                Trace.TraceWarning($"输入坐标[{row}, {col}]超出地图范围");
                return false;
            }
        }

        public Hex[,] GetMapData()// 取得地图六角格数据
        {
            return hexes;
        }

        public List<int[]> GetNeighbors(int[] pos)// 取得输入六角格的邻格
        {
            if (!IsValid(pos))// 如果是不合规的坐标
            {
                return null;// 返回null值
            }
            int row = pos[0];// 取得行
            int col = pos[1];// 取得列
            return hexes[row, col].neighbors;// 返回邻格数据
        }
        public List<int[]> GetNeighbors(int row, int col)// 重载方法 ↑
        {
            if (!IsValid(row, col))
            {
                return null;
            }
            return hexes[row, col].neighbors;
        }

        //public Dictionary<int[], float> GetHexCosts(int[] pos, int moveMode)// 取得到邻域的通行代价，通行代价定义为当前通行模式下最大机动速度/当前速度
        //{
        //    if (!IsValid(pos))// 如果是不合规的坐标
        //    {
        //        return null;// 返回null值
        //    }
        //    if (!Enum.IsDefined(typeof(MoveMode), moveMode))
        //    {
        //        return null;
        //    }
        //    int row = pos[0];// 取得行
        //    int col = pos[1];// 取得列
        //    return cost[moveMode][row][col];// 返回取得的通行代价
        //}

        //public Dictionary<int[], float> GetHexCosts(int[] pos, MoveMode moveMode)// 取得到邻域的通行代价，通行代价定义为当前通行模式下最大机动速度/当前速度
        //{
        //    if (!IsValid(pos))// 如果是不合规的坐标
        //    {
        //        return null;// 返回null值
        //    }
        //    int row = pos[0];// 取得行
        //    int col = pos[1];// 取得列
        //    return cost[(int)moveMode][row][col];// 返回取得的通行代价
        //}

        //public Dictionary<int[], float> GetHexCosts(int row, int col, int moveMode)// 重载方法 ↑
        //{
        //    if (!IsValid(row, col))
        //    {
        //        return null;
        //    }
        //    if (!Enum.IsDefined(typeof(MoveMode), moveMode))
        //    {
        //        return null;
        //    }
        //    return cost[moveMode][row][col];
        //}

        //public Dictionary<int[], float> GetHexCosts(int row, int col, MoveMode moveMode)// 重载方法 ↑
        //{
        //    if (!IsValid(row, col))
        //    {
        //        return null;
        //    }
        //    return cost[(int)moveMode][row][col];
        //}

        public bool CanSee(int[] pos1, int[] pos2, int mode = 0)// 判断以某种模式的两点之间是否通视
        {
            if (!IsValid(pos1))// 位置1不合规
            {
                return false;  // 出错就不能通视
            }
            if (!IsValid(pos2))// 位置1不合规
            {
                return false;
            }
            if (mode < 0 || mode >= see.shape[0])// 通视模式不合规
            {
                Trace.TraceWarning($"输入了不正确的通视模式: {mode}");
                return false;
            }
            int row1 = pos1[0];// 取得位置1的行
            int col1 = pos1[1];// 取得位置1的列
            int row2 = pos2[0];// 取得位置2的行
            int col2 = pos2[1];// 取得位置2的列
            return see[mode][row1, col1, row2, col2];// 返回取得的判断结果
        }
        /// <summary>
        /// 判断算子之间是否通视
        /// </summary>
        /// <returns></returns>
        public bool CanSeeTerrain(string type, string target_type, int[] opr_cur_hex, int[] target_cur_hex, string target_sub_type, Dictionary<string, int> opr_observe_distance, string target_move_state)
        {
            int mode = GetMode(type, target_type);// 取得通视模式
            if (mode < 0) return false;// 没有的通视模式

            if (!CanSee(opr_cur_hex, target_cur_hex, mode)) return false;// 己方算子和目标算子的所在六角格之间不能通视，则算子之间也不能通视

            int dist = GetDistance(opr_cur_hex, target_cur_hex);// 取得算子之间的距离
            // 坦克对坦克的视野最大是25格
            int observe_dist = opr_observe_distance[target_type];// 取得己方算子对目标类型的算子的最大观察距离

            int row = target_cur_hex[0];
            int col = target_cur_hex[1];
            int opr_row = opr_cur_hex[0];
            int opr_col = opr_cur_hex[1];

            // 优势地形，视距减半
            if (new List<TerrainType> { TerrainType.Forest, TerrainType.City }.Contains(hexes[row, col].cond))// 目标所在地形为森林或者居民地
            {
                observe_dist = observe_dist / 2;// 视距减半
            }

            // 目标高程优势+目标掩蔽，视距减半 飞机对车辆除外
            if ((hexes[opr_row, opr_col].elev <= hexes[row, col].elev) && target_move_state == "Shelt" && (type != "Aircraft" || target_type != "Vehicle"))
            {
                observe_dist = observe_dist / 2;// 视距减半
            }

            if (dist > observe_dist) return false;// 算子之间的距离大于视距，不可通视

            return true;// 否则可以通视
        }

        public int GetDistance(int[] pos1, int[] pos2)// 计算算子之间的距离
        {
            if (!IsValid(pos1))
            {
                return -1;  // -1表示不正确的距离
            }
            if (!IsValid(pos2))
            {
                return -1;
            }
            // Convert positions to cube coordinates
            int row1 = pos1[0];
            int col1 = pos1[1];
            int q1 = col1 - (row1 - (row1 & 1)) / 2;
            int r1 = row1;
            int s1 = -q1 - r1;
            int row2 = pos2[0];
            int col2 = pos2[1];
            int q2 = col2 - (row2 - (row2 & 1)) / 2;
            int r2 = row2;
            int s2 = -q2 - r2;
            // Calculate Manhattan distance
            return (Math.Abs(q1 - q2) + Math.Abs(r1 - r2) + Math.Abs(s1 - s2)) / 2;
        }

        /// <summary>
        /// 计算六角格中心坐标，width表示六角格的宽度，即相邻2个六角格中心的距离，一般为200米
        /// 六角格地图首行首列为第0行第0列，六角格尖顶朝上，奇数行（第1、3、5……）右缩
        /// </summary>
        /// <param name="pos">六角格偏移坐标，4位整型</param>
        /// <param name="width">六角格宽度，即相邻2个六角格中心的距离</param>
        /// <returns></returns>
        private Point2D CalcCentralCoordinates(int[] pos, int width)
        {
            Point2D point = new Point2D();
            int row = pos[0];
            int col = pos[1];
            float height = (float)(width / Math.Sqrt(3) * 2);
            if (row % 2 == 0)
            {
                point.X = (float)width * col + width / 2;
            }
            else
            {
                point.X = (float)width * col + width;
            }
            point.Y = (float)height * row + height / 2;
            return point;
        }

        /// <summary>
        /// 计算六角格pos1中心坐标到pos2中心的距离，单位米
        /// </summary>
        /// <param name="pos1">六角格偏移坐标，4位整型</param>
        /// <param name="pos2">六角格偏移坐标，4位整型</param>
        /// <returns></returns>
        public float GetDistanceInMeter(int[] pos1, int[] pos2)
        {
            Point2D center1 = new Point2D();
            Point2D center2 = new Point2D();
            float dist;
            if (!IsValid(pos1) || !IsValid(pos2))
            {
                return -1;
            }
            center1 = CalcCentralCoordinates(pos1, Width);
            center2 = CalcCentralCoordinates(pos2, Width);
            dist = (float)Math.Sqrt(Math.Pow(center1.X - center2.X, 2) + Math.Pow(center1.Y - center2.Y, 2));
            return dist;
        }

        /// <summary>
        /// 使用A*算法计算从begin到end的路径
        /// </summary>
        /// <param name="begin"></param>
        /// <param name="end"></param>
        /// <param name="mode"></param>
        /// <returns></returns>
        public List<int[]> GenMoveRoute(int[] begin, int[] end, int mode)
        {
            SortedSet<Tuple<float, float, int[]>> frontier = new SortedSet<Tuple<float, float, int[]>>();// 按item1排序，即neighCost；第二个数是随机数，第三个数是格子id（neighCost：起始格到当前格的距离 + 当前格到终点的距离）（其中距离表示的都是距离多少格）
            Dictionary<int[], float> cost_so_far = new Dictionary<int[], float>();// 到某格的代价，键为id，值为代价;(起始格键对应的值为0，即无代价)（为1就是无地形阻碍的正常代价）
            Dictionary<int[], int[]> came_from = new Dictionary<int[], int[]>();// 从哪来，值是键的上一格;(起始格键对应的值为-1，即无上一格)（键从哪来，即机动路径，最后用终点的六角格id往回退得到机动路径）


            if (!IsValid(begin))
            {
                return null;
            }
            if (!IsValid(end))
            {
                return null;
            }
            if (mode < 0 || mode >= cost.Count)
            {
                Trace.TraceWarning($"输入了不正确的通视模式: {mode}");
                return null;
            }
            if (begin == end)
            {
                Trace.TraceWarning($"机动路线计算中输入的起始位置和终止位置相同");
                return null;
            }

            // BFS with priority queue to find one of the fastest move path from `begin` to `end` with given `mode`
            frontier.Add(new Tuple<float, float, int[]>(0, (float)new Random().NextDouble(), begin));
            cost_so_far[begin] = 0;
            came_from[begin] = new int[] { -1, -1 };  // -1表示没有父节点

            while (frontier.Count > 0)
            {
                Tuple<float, float, int[]> current = frontier.First();
                frontier.Remove(current);// 取出frontier第一个值，删去（即删去 起点到此处的代价（不准确可认为是距离起点）+ 距离终点 的值最小的一格，不删掉会出现最小值永远被某一格占据的情况，删掉之后才能保证一直朝着终点前进）（不删掉的话，万一出现机动一次之后 代价+1但是距离-0 或者 代价加1.几距离-1 等情况 就会取到之前的计算过的值）、、、、、、、、、、（应该不会有万一：：想多了：：：：万一出现不是朝着终点前进的情况，因为距离终点的距离增加且（原因：上一格被删了，往回走的情况被杜绝了），所以一定不会被取出）（出现新一格的值比之前的大而走之前的格子的情况，也会因为越走越歪，值越大，还是会取到朝着终点前进的值）

                //float cost = current.Item2;
                int[] cur = current.Item3;// 当前所在格
                int row = cur[0];
                int col = cur[1];

                if (cur.SequenceEqual(end)) break;// 如果已经到终点，退出

                foreach (float[] neigh_n_cost in cost[mode][row][col])// 当前所在格的邻格，以及代价
                {
                    int[] neigh = new int[] { (int)neigh_n_cost[0], (int)neigh_n_cost[1] };// 邻格
                    float edge_cost = neigh_n_cost[2];// 代价（相当于走了多少格）
                    float neighCost = cost_so_far[cur] + edge_cost;// 起点到当前格的代价 + 到邻格的代价

                    if (cost_so_far.ContainsKey(neigh) && cost_so_far[neigh] < neighCost) continue; // 如果之前已经计算过到 当前格邻格 ，且代价小于本次计算，不修改代价继续下一次循环（保留之前代价小的路径，然后继续循环确保了不会往回走）
                    cost_so_far[neigh] = neighCost;// 如果不小于或者没有计算过，添加键值对（邻格id，和起点到邻格的代价）
                    came_from[neigh] = cur;// 添加邻格和到邻格路径的上一格（当前格cur）
                    int heuristic = this.GetDistance(neigh, end);// 计算新一格到终点的距离（距离多少格）
                    frontier.Add(new Tuple<float, float, int[]>(neighCost + heuristic, (float)new Random().NextDouble(), neigh));// item1：本格到邻格（走到的最新的一格）的代价 + 新的格子到终点的距离， item3：走到的最新的一格
                }
                // 第一次遍历完一遍后，frontier添加了到全部邻格的距离加代价，然后第二次取出的第一个值是（到当前格邻格代价 + 邻格到终点的距离）最小的邻格，作为当前格 ===> 即作为机动路径的其中一格
                // 代价是1 就是 普通 走一格 的代价，GetDistance计算出的距离也是 距离多少格 的 值
            }

            List<int[]> path = new List<int[]>();

            if (came_from.ContainsKey(end))
            {
                int[] cur = end;
                while (cur.SequenceEqual(begin))
                {
                    path.Add(cur);
                    cur = came_from[cur];
                }
                path.Reverse();
            }

            // 就结果而言， cost_so_far的键包含了机动路径path一路上的每一格，其全部邻格。 值即为起点到键的最小代价
            // came_from的值为 起点到键的最小路径上的 键的上一格
            return path;
        }

        /// <summary>
        /// 取得算子之间的通视模式
        /// </summary>
        /// <param name="opr_type"></param>
        /// <param name="target_type"></param>
        /// <returns></returns>
        public int GetMode(string opr_type, string target_type)
        {
            if ((opr_type == "Infantry" || opr_type == "Vehicle") && (target_type == "Infantry" || target_type == "Vehicle"))// 己方是地面算子，目标也是地面算子
            {
                return 0;  // 地对地模式
            }
            else if ((opr_type == "Infantry" || opr_type == "Vehicle") && target_type == "Aircraft")// 己方是地面算子，目标是空中算子
            {
                return 2;  // 地对空模式
            }
            else if ((target_type == "Infantry" || target_type == "Vehicle") && opr_type == "Aircraft")// 己方是空中算子，目标是地面算子
            {
                return 2;  // 空对地模式
            }
            else if (opr_type == "Aircraft" && target_type == "Aircraft")// 己方是空中算子，目标是空中算子
            {
                return 1;  // 空对空模式
            }
            else
            {
                Trace.TraceWarning($"暂时不能计算此输入的同时模式");
                return -1;  // 无此模式
            }
        }

        //public bool ContainsHex(List<int[]> hexes, int[] hex)
        //{
        //    return hexes.Any(h => h.SequenceEqual(hex));
        //}

        //public bool ContainsHex(Dictionary<int[], dynamic> obj, int[] hex)
        //{
        //    return obj.Any(h => h.Key.SequenceEqual(hex));
        //}

        //public dynamic GetCostsValue(Dictionary<int[], dynamic> obj, int[] key)
        //{
        //    if (ContainsHex(obj, key))
        //    {
        //        return obj.FirstOrDefault(o => o.Key.SequenceEqual(key)).Value;
        //    }
        //    else return null;
        //}
        public dynamic GetCost(int[] pos, int[] nextPos, MoveMode moveMode)
        {
            if (!IsValid(pos))// 如果是不合规的坐标
            {
                return null;// 返回null值
            }
            int row = pos[0];// 取得行
            int col = pos[1];// 取得列

            List<float[]> curCosts = cost[(int)moveMode][row][col];
            foreach (float[] curCost in curCosts)
            {
                if (curCost[0] == nextPos[0] && curCost[1] == nextPos[1])
                {
                    return curCost[2];
                }
            }
            //if (curCost.Any(c => c.Key.SequenceEqual(nextPos)))//判断有无值相等的键
            //{
            //    return curCost.First(o => o.Key.SequenceEqual(nextPos)).Value;// 返回取得的通行代价，如果是FirstOrDefault没找到会返回[,0]，如果是Fist没找到会出异常
            //}
            return null;
        }
        public dynamic GetCost(int[] pos, int[] nextPos, int moveMode)
        {
            if (!IsValid(pos))// 如果是不合规的坐标
            {
                return null;// 返回null值
            }
            int row = pos[0];// 取得行
            int col = pos[1];// 取得列
            //Dictionary<int[], float> curCost = cost[moveMode][row][col];
            //if (curCost.Any(c => c.Key.SequenceEqual(nextPos)))
            //{
            //    return curCost.First(o => o.Key.SequenceEqual(nextPos)).Value;// 返回取得的通行代价
            //}
            List<float[]> curCosts = cost[moveMode][row][col];
            foreach (float[] curCost in curCosts)
            {
                if (curCost[0] == nextPos[0] && curCost[1] == nextPos[1])
                {
                    return curCost[2];
                }
            }
            return null;
        }

        public int GetNeighborIndex(int[] pos, int[] neighbor)
        {
            if (!IsValid(pos))// 如果是不合规的坐标
            {
                return -1;// 返回-1
            }
            int row = pos[0];// 取得行
            int col = pos[1];// 取得列
            return hexes[row, col].neighbors.FindIndex(n => n.SequenceEqual(neighbor));// 返回索引，没找到返回的是-1
        }

        /// <summary>
        /// 二维点
        /// </summary>
        private struct Point2D
        {
            public float X;
            public float Y;
        }
    }
}
